// TODO: CODE REVIEW & CLEANUP!
//
// ServerEndpoint.cs
//
// Implements ServerEndpoint and related types.
//

using System;
using System.Diagnostics;
using System.Xml;
using HomeUX.Server;
using HomeUX.Utilities;

namespace HomeUX.Networking
{

[DebuggerDisplay("ServerEndpoint for Client#{ShortSessionId} ({SessionId})")]
public class ServerEndpoint : MessageEndpoint, IMessageEndpoint, IServerEndpointBase
{
    IMessageEndpoint Endpoint { get; set; }

    public event EventHandler<MessageReceivedEventArgs> MessageReceived;

    SocketConnection _connection;
    WorkQueue _workQueue;

    // valid for lifetime of client?; allocated by client?
    public Guid SessionId { get; private set; }

    // valid for lifetime of server; allocated by server?
    public int ShortSessionId { get; private set; }

    public object Tag { get; set; }

    public bool IsConnected
    {
        get
        {
            return SessionId != Guid.Empty;
        }
    }

    public event EventHandler IsConnectedChanged;

    public ServerEndpoint(SocketConnection connection, string password, WorkQueue workQueue)
    {
        _workQueue = workQueue;
        _connection = connection;
        Password = password;
    }

    protected override bool SendRawMessage(Message message)
    {
        _workQueue.AssertIsWorkQueueThread();
        return _connection.SendBufferList(message.GetBufferList());
    }

    public void Dispose()
    {
        _workQueue.AssertIsWorkQueueThread();
        if (!IsDisposed)
        {
            var messageEndpoint = (MessageEndpoint)this;
            ((IDisposable)messageEndpoint).Dispose();
            _connection.Disconnect();
        }
    }

    public new void OnDataReceived(byte[] buffer, int bytesReceived)
    {
        _workQueue.AssertIsWorkQueueThread();
        base.OnDataReceived(buffer, bytesReceived);
    }

    // returns false if e.g. not connected
    public new bool SendMessage(Message message)
    {
        Util.Trace("Sent {0} ({1} bytes) to Client#{2}", message.Name,
            ((message.Body != null) ? message.Body.Length : 0), ShortSessionId);
        _workQueue.AssertIsWorkQueueThread();
        return base.SendMessage(message);
    }

    protected override void EndConnection(Exception ex)
    {
        _workQueue.AssertIsWorkQueueThread();
        base.EndConnection(ex);
        _connection.Disconnect();
        if (IsConnected)
        {
            // we have a session -- clear SessionId, and notify the application
            SessionId = Guid.Empty;
            if (IsConnectedChanged != null)
                IsConnectedChanged(this, EventArgs.Empty);
        }
    }

    protected override void OnMessageReceived(Message message)
    {
        _workQueue.AssertIsWorkQueueThread();

        // ignore test messages -- these are sent by the client frequently (e.g. once per second)
        // so we don't want to bother the application with them
        if (message.Name == "Test")
            return;

        // if the client is sending a "Session" message, to establish a session 
        if (message.Name == "Session")
        {
            // ignore this message if we're already connected
            if (IsConnected)
                return;

            message.EnumerateMessageElements(delegate(XmlReader reader)
            {
                switch (reader.Name)
                {
                case "SessionId":

                    string sessionIdString = reader.ReadElementContentAsString();
                    try
                    {
                        SessionId = new Guid(sessionIdString);
                        ShortSessionId = ShortSessionIdManager.AllocateShortSessionId(SessionId);
                    }
                    catch (Exception ex)
                    {
                        if ((ex is FormatException) || (ex is OverflowException))
                        {
                            Dispose();
                            return;
                        }
                        else
                            throw;
                    }

                    if (IsConnectedChanged != null)
                        IsConnectedChanged(this, EventArgs.Empty);

                    break;
                }
            });
            return;
        }

        // process the message on the work queue thread, using the application-provided
        // delegate
        if (MessageReceived != null)
        {
			MessageReceived(this, new MessageReceivedEventArgs()
			{
				Message = message
			});
        }
    }
}

}
